#ifndef _STATEMACHINE_H_
#define _STATEMACHINE_H_

#include <memory>
#include <variant>
#include <unordered_map>
#include <optional>
#include <SECore/StateMachine/State.h>
#include <SECore/StateMachine/StateEvent.h>
#include <SECore/Timer.h>

namespace SEngine
{
    class StateMachine
    {
    public:
        StateMachine() { m_timer = std::make_unique<Timer>(); }
        virtual ~StateMachine() = default;

        void Start(State & entry, int ms) {
            if (m_states.find(entry.uuid) != m_states.end()) {
                m_curState = m_states.at(entry.uuid);
                m_curState.lock()->OnEntry();
                m_timer->Run(ms, std::bind(&StateMachine::OnUpdate, this, ms), false);
            }
        }

        void Stop() {
            if (!m_curState.expired()) {
                m_curState.lock()->OnExit();
            }
            m_timer->Release();
        }

        void RemoveState(State & state) {
            if (m_states.find(state.uuid) != m_states.end()) {
                m_states.at(state.uuid)->SignalRemoveSelf.Invoke();
                m_states.erase(state.uuid);
            }
        }

        template<typename T, typename... Args>
        T & CreateState(Args&&... args) {
            auto statePtr = std::make_shared<T>(std::forward<Args>(args)...);
            auto & state = *statePtr;
            m_states.emplace(statePtr->uuid, std::move(statePtr));
            return state;
        }
    
        std::unique_ptr<StateEvent> CreateStateEvent(State& from, State& to) {
            auto event = std::make_unique<StateEvent>();
            event->SignalTransform.AddSlot([&, &e = *event](bool & ret) {
                ret = this->Transform(from.uuid, to.uuid, e);
                return SlotState::Keep;
            });
            return event;
        }

        void ForeachState(std::function<void(State&)> && fn) {
            for (auto & [uuid, state] : m_states) {
                fn(*state);
            }
        }

    private:
        bool Transform(const std::string & from, const std::string & to, StateEvent & event) {
            if (!m_curState.expired()) {
                if (m_curState.lock()->uuid == from) {
                    auto fromState = GetState_(from);
                    auto toState = GetState_(to);
                    if (fromState && toState && event.Condition(fromState->get(), toState->get())) {
                        fromState->get().OnExit();
                        m_curState = m_states.at(toState->get().uuid);
                        toState->get().OnEntry();
                        return true;
                    }
                }
            }
            return false;
        }

        std::optional<std::reference_wrapper<State>> GetState_(const std::string & uuid) {
            if (m_states.find(uuid) == m_states.end()) {
                return std::nullopt;
            }
            return *m_states.at(uuid);
        }

    public:
        void OnUpdate(int delta) {
            if (!m_curState.expired()) {
                m_curState.lock()->OnState();
            }
        }

    private:
        std::unordered_map<std::string, std::shared_ptr<State>> m_states;
        std::weak_ptr<State> m_curState;
        std::unique_ptr<Timer> m_timer;
    };
} // namespace SEngine


#endif